﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Runtime.Serialization;
using System.ComponentModel;


namespace XMas
{
    /// <summary>
    ///  Node that have a list of values and select one or two values depending of range
    /// </summary>
    [Serializable()]
    public class AnimNode_RangeCrossFade : AnimNode_CrossFade
    {

        #region Fields

        /// <summary>
        /// Number of version of this subnode, used in serialization
        /// </summary>
        /// <remarks>
        /// If you add a new value to serialization, you need to increment this float
        /// This is need to have compatibility with olders version of library
        /// <see cref="WriteXml(System.Xml.XmlWriter)"/>
        /// </remarks>        
        protected const float _SerializationVersionRangeCrossFade = 1.0f;
        /// <summary>
        /// Range value list
        /// </summary>
        protected List<float> _RangeValueList;
        /// <summary>
        /// Set true if we are in editor
        /// </summary>
        protected bool _bIsInEditor = false;

        #endregion


        /// <summary>
        /// Contructor
        /// </summary>
        public AnimNode_RangeCrossFade()
        {
        }


        #region Properties

        /// <summary>
        /// Atribute for _RangeValueList
        /// <see cref="AnimNode_RangeCrossFade._RangeValueList"/>
        /// </summary>
        [CategoryAttribute("Ranged CrossFade"), DescriptionAttribute("Range value list")]
        public List<float> RangeValueList
        {
            get { return _RangeValueList; }
            set { 
                    _RangeValueList = value;                     
                    UpdateRangeList();
                }
        }

        #endregion


        # region Protected Members

        /// <summary>
        /// Initialization of node, set initial values
        /// </summary>
        /// <param name="ID">Node ID</param>
        protected override void Initialize(System.Guid ID)
        {
            _RangeValueList = new List<float>();

            base.Initialize(ID);
            _strName = "Ranged Cross Fade";
            _bFixedChildren = false;
            _bIsInEditor = false;
        }

        /// <summary>
        /// Check and update that range list has the same item numbers
        /// </summary>
        protected void UpdateRangeList()
        {
            _RangeValueList.Sort();
            // We need same numbers of elements that children
            int iDiff = _animNodeItemList.Count - _RangeValueList.Count;
            if (iDiff < 0)
                _RangeValueList.RemoveRange(_RangeValueList.Count + iDiff, -iDiff);
            if (iDiff > 0)
            {
                float fValue = 0;
                if (_RangeValueList.Count > 0)
                    fValue = _RangeValueList[_RangeValueList.Count - 1];
                for (int i = 0; i < iDiff; ++i)
                    _RangeValueList.Add(fValue);
            }
        }

        #endregion


        # region Public Members

        /// <summary>
        /// Add new child to this node
        /// </summary>
        /// <param name="ItemText">Child text</param>
        /// <param name="fInitialWeigth">Child initial weigth </param>
        /// <returns>New item</returns>
        public override AnimationNodeItem AddChild(string ItemText, float fInitialWeigth)
        {
            AnimationNodeItem NodeItem = base.AddChild(ItemText, fInitialWeigth);
            UpdateRangeList();

            return NodeItem;
        }

        /// <summary>
        /// Remove a child from this node
        /// </summary>
        /// <param name="iIdxNode">Child index </param>
        /// <returns>True if the children has been removed</returns>
        public override bool RemoveChild(int iIdxNode)
        {
            bool bResult = base.RemoveChild(iIdxNode);
            if (bResult && iIdxNode < _RangeValueList.Count)
                _RangeValueList.RemoveAt(iIdxNode);
            UpdateRangeList();

            return bResult;
        }

        /// <summary>
        /// EDITOR EVENT: Called by editor when slider move
        /// </summary>
        /// <param name="iNewValue">Value is between 0 and 100</param>
        public override void HandleSliderMove(int iNewValue)
        {
            // int value is between 0 and 100
            _fEditorValue = (float)iNewValue / 100.0f;
            _bIsInEditor = true;
        }

        /// <summary>
        /// Used by system when the node need to update his weight
        /// </summary>
        /// <param name="DeltaSeconds">Elapsed time</param>
        public override void UpdateWeights(double DeltaSeconds)
        {
            // Calculate current value
            CalcCurrentValueWithSpeed(GetValue(), (float)DeltaSeconds);

            // Calculate range
            int idx = 0;
            for (idx = 0; idx < _RangeValueList.Count - 1; ++idx)
            {
                // In range
                if (_fCurrentValue >= _RangeValueList[idx] && _fCurrentValue <= _RangeValueList[idx + 1])
                {
                    float fRange = _RangeValueList[idx + 1] - _RangeValueList[idx];
                    float fPercent = 0;
                    if (fRange != 0)
                        fPercent = (_fCurrentValue - _RangeValueList[idx]) / fRange;

                    _animNodeItemList[idx]._fLocalWeigth = 1.0f - fPercent;
                    _animNodeItemList[idx + 1]._fLocalWeigth = fPercent;
                    idx += 2;   // Don't want to clear this node and next node
                    break;
                }
                else
                    _animNodeItemList[idx]._fLocalWeigth = 0;
            }

            // Clear the other items
            for (int i = idx; i < _animNodeItemList.Count; ++i)
                _animNodeItemList[i]._fLocalWeigth = 0;            
        }

        /// <summary>
        /// Internal get current value, first try to use reflexion. If no reflexion found use editor value
        /// Override with caution
        /// </summary>
        /// <returns>Value from initialRange to endRange</returns>
        protected override float GetValue()
        {
            // We need a value from initialRange to endRange
            float fResultValue = base.GetValue();
            if (_RangeValueList.Count > 0)
            {
                if (_bIsInEditor)
                {
                    // Transform to range
                    fResultValue = _RangeValueList[0] + (fResultValue * (_RangeValueList[_RangeValueList.Count - 1] - _RangeValueList[0]));
                }
                else
                {
                    // Clamp            
                    if (fResultValue < _RangeValueList[0])
                        fResultValue = _RangeValueList[0];
                    if (fResultValue > _RangeValueList[_RangeValueList.Count - 1])
                        fResultValue = _RangeValueList[_RangeValueList.Count - 1];
                }
            }

            return fResultValue;
        }

        /// <summary>
        /// Used by system after the node update his weight
        /// </summary>
        /// <param name="elapsedTime"></param>
        public override void PostUpdateWeights(TimeSpan elapsedTime)
        {
        }

        #endregion


        #region Load/Save XML

        /// <summary>
        /// Function to write in XML format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the save order, it must be the same that read order!!
        /// </remarks>
        /// <param name="writer">Writer to store data</param>
        public override void WriteXml(XmlWriter writer)
        {
            base.WriteXml(writer);

            writer.WriteElementString("SerializationVersionRangeCrossFade", _SerializationVersionRangeCrossFade.ToString(new System.Globalization.CultureInfo("en-US")));
            writer.WriteStartElement("RangeList", null);
            // Item list
            foreach (float fValue in _RangeValueList)
            {
                writer.WriteStartElement("Range", null);
                writer.WriteElementString("Value", fValue.ToString(new System.Globalization.CultureInfo("en-US")));
                writer.WriteEndElement();
            }
            writer.WriteEndElement();     
        }

        /// <summary>
        /// Function to read xml format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the read order, it must be the same that save order!!
        /// </remarks>
        /// <param name="reader">Reader to retrieve data</param>
        public override void ReadXml(XmlReader reader)
        {
            base.ReadXml(reader);

            float SerializationVersionRangeCrossFade = reader.ReadElementContentAsFloat("SerializationVersionRangeCrossFade", reader.NamespaceURI);
            // Get range list
            _RangeValueList = new List<float>();
            reader.ReadStartElement("RangeList");
            bool bFound = false;
            while (reader.IsStartElement("Range", reader.NamespaceURI))
            {
                reader.ReadStartElement();
                float fValue = reader.ReadElementContentAsFloat("Value", reader.NamespaceURI);
                reader.ReadEndElement();
                _RangeValueList.Add(fValue);
                bFound = true;
            }
            UpdateRangeList();
            // </RangeList> only if items found
            if (bFound)
                reader.ReadEndElement();
        }

        #endregion
    }
}
